home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 March: Reference Library / Dev.CD Mar OO RLDisk 1.toast / pc / what's new / development kits / hardware / mac os usb ddk 1.4f3 / examples / hidreader / hidreader.c next >
Encoding:
C/C++ Source or Header  |  2000-01-19  |  15.9 KB  |  461 lines

  1. /*
  2.     File:        HIDReader.c
  3.  
  4.     Contains:    HID Library Example
  5.  
  6.     Version:    1.0 for use with USB DDK 1.4
  7.  
  8.  
  9.     Copyright:    © 1999 by Apple Computer, Inc., all rights reserved.
  10.  
  11.  
  12.         To get current values and set them, this code requires USB 1.4, which
  13.     is newer than Mac OS 9. The example is written with enough checking
  14.     built in that it can safely execute on earlier USB versions.
  15.         This example addresses a number of issues involved with using the HID
  16.     Library to interact with Human Input USB Devices. I have tried to group
  17.     the code for each issue in a single module separated by comments. For
  18.     this reason, not all sections may be necessary for your use. Even within
  19.     a section there may be more code than you need to use. Error checking
  20.     and memory allocation is rather simplistic to easily survive the affects
  21.     of removing unnecessary code.
  22. */
  23.  
  24. #define CALL_NOT_IN_CARBON 1
  25.  
  26. #include <stdio.h>
  27. #include <stdlib.h>
  28. #include <errors.h>
  29. #include <USB.h>
  30. #include <HID.h>
  31.  
  32.     //    How do we find out what vendor ID and product ID we are looking for?
  33.     // Use USB Prober to check under the Device Descriptor.
  34.     //    I have also used USB Prober to examine the Parsed Report Descriptor
  35.     // under the device's Configuration Descriptor. I chose a usage that i
  36.     // knew allowed values to be set. In this case RemainingCapacityLimit, 
  37.     // which is usage 41 on HID usage page 133. Find the listing for this
  38.     // item in the descriptor and scan upward to find report ID and report
  39.     // size that correspond to it. Also note that the item is described as
  40.     // Feature(Data, Variable, Absolute, Non-volatile). In the same fashion
  41.     // it may be possible for you to extract the necessary information to 
  42.     // interact with the HID item of your choice.
  43.     #define myVendorID            0x051D
  44.     #define myProductID            0x0002
  45.  
  46.     #define targetUsage            41
  47.     #define targetUsagePage        133
  48.     #define targetReportID        17
  49.     #define targetReportSize    24
  50.  
  51.  
  52.     // Forward declarations:
  53.     void InstallReportHandler();
  54.     void RemoveReportHandler();
  55.     void MyHIDReportHandler(void * inHIDReport, UInt32 inHIDReportLength, 
  56.                                     UInt32 inRefcon);
  57.         
  58.     // Data shared between functions
  59.     HIDDeviceDispatchTablePtr    mHIDDispatchTable;
  60.     USBDeviceDescriptorPtr        mUSBDeviceDesc;
  61.     HIDPreparsedDataRef            mParsedHIDRef;
  62.     HIDDeviceConnectionRef        mHIDDeviceConnectionRef;
  63.  
  64.     UInt32                         mReportID = targetReportID;
  65.     UInt32                         mCollection = 0;
  66.  
  67.  
  68. int main(void)
  69. {
  70.     // Data shared between sections
  71.     CFragConnectionID    usbConnID;
  72.     CFragSymbolClass    symClass;
  73.     THz                    currentZone;
  74.     OSErr                 err;
  75.  
  76.  
  77. // ***** Step 1: Find Your Device:
  78.  
  79.     USBDeviceRef        usbDeviceRef = kNoDeviceRef;
  80.     Boolean                foundMyDevice = false;
  81.  
  82.     while (!foundMyDevice)
  83.     {
  84.         err = USBGetNextDeviceByClass (&usbDeviceRef, &usbConnID, kUSBHIDClass,
  85.                                         kUSBAnySubClass, kUSBAnyProtocol);
  86.         if (err) return err;
  87.  
  88.         // Need to be in the system zone when we search for the symbol.
  89.         currentZone = GetZone ();
  90.         SetZone (SystemZone ());
  91.         err = FindSymbol (usbConnID, "\pTheUSBDriverDescription", 
  92.                             (Ptr *)&mUSBDeviceDesc, &symClass);
  93.         SetZone (currentZone);
  94.  
  95.         if (mUSBDeviceDesc->vendor == myVendorID &&
  96.             mUSBDeviceDesc->product == myProductID)
  97.         {
  98.             foundMyDevice = true;
  99.         }
  100.         
  101.         // If the driver that matched our device was the HID class driver,
  102.         // it does not belong to a specific device, so it has vendor and
  103.         // product ids of 0, 0. In that case, we have to check farther.
  104.         
  105.         if (mUSBDeviceDesc->vendor == 0 &&
  106.             mUSBDeviceDesc->product == 0)
  107.         {
  108.             // Now we are going to get information from the device itself.
  109.             currentZone = GetZone();
  110.             SetZone(SystemZone());
  111.             err = FindSymbol(usbConnID, "\pTheHIDDeviceDispatchTable", 
  112.                                 (Ptr *)&mHIDDispatchTable, &symClass);
  113.             SetZone(currentZone);
  114.             if (err) continue;        // There may be other devices to check.
  115.  
  116.             UInt16 theVendorID = 0;
  117.             UInt32 size = sizeof(UInt16);
  118.             err = (*mHIDDispatchTable->pHIDGetDeviceInfo)(kHIDGetInfo_VendorID,
  119.                                                     &theVendorID, &size);
  120.             if (err) continue;
  121.  
  122.             UInt16 theProductID = 0;
  123.             size = sizeof(UInt16);
  124.             err = (*mHIDDispatchTable->pHIDGetDeviceInfo)(kHIDGetInfo_ProductID, 
  125.                                                     &theProductID, &size);
  126.             if (err) continue;
  127.  
  128.             if (theVendorID == myVendorID && theProductID == myProductID)
  129.             {
  130.                 foundMyDevice = true;
  131.             }
  132.         }
  133.     }
  134.     
  135.     //    Don't have to check foundMyDevice, since only other way out
  136.     // of loop was return.
  137.  
  138.  
  139. // ***** Step 2: HID Library Setup:
  140.     
  141.     #define kMaxReportDescSize    1024
  142.  
  143.     UInt8 *        mHIDReportDesc;
  144.     UInt32        mHIDReportDescLength;
  145.  
  146.     currentZone = GetZone();
  147.     SetZone(SystemZone());
  148.     err = FindSymbol(usbConnID, "\pTheHIDDeviceDispatchTable", 
  149.                         (Ptr *)&mHIDDispatchTable, &symClass);
  150.     SetZone(currentZone);
  151.     if (err) return err;
  152.  
  153.     mHIDReportDesc = (UInt8 *)NewPtrClear(kMaxReportDescSize);
  154.     if (mHIDReportDesc == nil) return MemError();
  155.  
  156.     mHIDReportDescLength = kMaxReportDescSize;
  157.     err = (*mHIDDispatchTable->pHIDGetHIDDescriptor)(kUSBReportDesc, 0, 
  158.                                         mHIDReportDesc, &mHIDReportDescLength);
  159.     if (!err)
  160.         err = HIDOpenReportDescriptor(mHIDReportDesc, mHIDReportDescLength, 
  161.                                 &mParsedHIDRef, kHIDFlag_StrictErrorChecking);
  162.     // Some HID report descriptors may have minor errors that trip up our call
  163.     // when kHIDFlag_StrictErrorChecking is on. If may be possible to try this
  164.     // with 0 error checking and have the open succeed.
  165.     
  166.     DisposePtr((char *)mHIDReportDesc);
  167.  
  168.     if (err) goto finalcleanup;
  169.     
  170.     
  171.     // ***** Step 2b: Dynamically Finding Report Info:
  172.     
  173.     //    HIDValueCaps & HIDButtonCaps may be necessary for info to use in HID
  174.     // calls. (For what information is actually necessary, see the discussion
  175.     // in the comments from sections on "Get Current Value" and "Change Value".)
  176.     // For this example, i know that i want to work with a feature value type of
  177.     // HID element and that i would like to find it's reportID and "collection".
  178.     //    Note: If we have already determined these values from USB Prober, there
  179.     // is no need to go through this dynamic lookup.
  180.  
  181.     HIDCaps            mMaxCaps;
  182.     UInt32            numFeatureValues;
  183.     HIDValueCaps *    vcPtr = nil;
  184.     // For buttons, use HIDButtonCaps.
  185.     
  186.     // Get statistics on how many of each type of HID item.
  187.     err = HIDGetCaps(mParsedHIDRef, &mMaxCaps);
  188.  
  189.     if (!err)
  190.     {    // There can be 6 arrays of HID report info: Both values and buttons can
  191.         // be grouped into kHIDInputReport, kHIDOutputReport, and kHIDFeatureReport.
  192.         // For simplicity, we will only check for a feature value at this time.
  193.         numFeatureValues = mMaxCaps.numberFeatureValueCaps;
  194.         vcPtr = 
  195.             (HIDValueCaps *)NewPtrClear(sizeof(HIDValueCaps) * numFeatureValues);
  196.             
  197.         if (vcPtr != nil)
  198.         {
  199.             err = HIDGetValueCaps(kHIDFeatureReport, vcPtr, &numFeatureValues, 
  200.                                     mParsedHIDRef);
  201.             if (!err)
  202.             {    // We have our information and can search for specific case.
  203.                 for (int i = 0; i < numFeatureValues; i++)
  204.                 {
  205.                     // Before treating the usage field as a real usage, we
  206.                     // should have checked the isRange flag. However, we
  207.                     // won't match the targetUsage even if it is usageMin.
  208.                     if (vcPtr[i].u.notRange.usage == targetUsage &&
  209.                         vcPtr[i].usagePage == targetUsagePage)
  210.                     {
  211.                         mReportID = vcPtr[i].reportID;
  212.                         mCollection = vcPtr[i].collection;
  213.                         break;
  214.                     }
  215.                 }
  216.             }
  217.  
  218.             DisposePtr((char *)vcPtr);
  219.         }
  220.     }
  221.  
  222.     if (err) goto finalcleanup;
  223.     
  224.  
  225. // ***** Step 3: Setup Report Handler:
  226.     
  227.     // The report handler will normally just let us know when a value has changed.
  228.     // But what if we want to Get an initial value?
  229.     // Normally we would do some sychronous read of the value we are interested in.
  230.     // However, USB is running at interrupt time and HID Library has gone to great
  231.     // lengths to shield us from that. So the way to cleanly get a value is to use
  232.     // the report handler. We will install the report handler and then request a
  233.     // report for the value we are interested in.
  234.  
  235.     InstallReportHandler();
  236.     
  237.  
  238. // ***** Step 4: Get Current Value:
  239.     
  240.     //    Installing the report handler will let us know when values change
  241.     // because that is when reports are issued. To get initial values, however,
  242.     // we may have to request them.
  243.     
  244.     // Prior to USB 1.4, the pHIDGetReport vector was nil, so there was no
  245.     // way to get a current value until after Mac OS 9.0.
  246.     if (mHIDDispatchTable->pHIDGetReport == nil) goto waitforinput;
  247.     
  248.     // We are not only asking for the value we are interested in, but also
  249.     // any value that shares that reportID.
  250.     err = (*mHIDDispatchTable->pHIDGetReport)(mHIDDeviceConnectionRef, 
  251.                 kHIDFeatureReport, mReportID, MyHIDReportHandler, 0);
  252.  
  253.  
  254. // ***** Step 5: Change Value:
  255.  
  256.     // To use SetValue, we discovered that we needed more information than
  257.     // was easily available. To recitify this, future releases of the HID
  258.     // Library may include an expanded API that will handle some of the 
  259.     // setup we are doing here.
  260.  
  261.     // Prior to USB 1.4, the pHIDSetReport vector was nil, so there was no
  262.     // way to get a current value until after Mac OS 9.0.
  263.     if (mHIDDispatchTable->pHIDSetReport == nil) goto waitforinput;
  264.  
  265.     // Create storage for the HID report we are going to send.
  266.     // The first thing we need to know is what size record to create. There
  267.     // is NO API in the HID Library that allows us to find this out. So for
  268.     // now, we must look at the HID Report Descriptor displayed by USB Prober.
  269.     // Remember that the pertinent size is the one that is listed closest
  270.     // above the item you are interested in's usage. The size displayed there
  271.     // is in bits, but the size we use in HID Library calls is in byte. In this
  272.     // example we have a size of 24. To get bytes we use the formula:
  273.     // bytes = (bits + 7) / 8, which keeps us from rounding off those odd 
  274.     // numbers of bits. Not only that, but when there are many possible 
  275.     // reportID's, we need an extra byte to hold the reportID we want. 
  276.     // So in our example we have found the byte size to be 4. Complicating
  277.     // this is the fact that many HID devices have only a default report type
  278.     // that effectively has a reportID of 0. In those cases, the HID spec lets
  279.     // us transmit just the raw values without the extra ID byte. One way to
  280.     // recognize this is if the call with your original size calculation returns
  281.     // an error value of kHIDInvalidReportLengthErr, you can step the size down
  282.     // by 1. (Clearly we need an API to fully construct these reports that takes
  283.     // all of this into account.)
  284.     
  285.     UInt32 newValue = 30;    // Random number for our example.
  286.     UInt32 reportSize = 4;
  287.     UInt8 * reportPtr = (UInt8 *)::NewPtrClear(reportSize);
  288.     if (reportPtr == nil) goto waitforinput;
  289.     
  290.     // In the case of not needing the reportID, mReportID = 0, so we're OK.
  291.     *reportPtr = mReportID;
  292.     
  293.     // The HID Library can now build the necessary HID report. Note here that
  294.     // a collection value of 0 is usually sufficient for the setup calls.
  295.     err = HIDSetUsageValue(kHIDFeatureReport, targetUsagePage, mCollection, 
  296.                 targetUsage,  newValue, mParsedHIDRef, reportPtr,  reportSize);
  297.     if (err) goto setvaluecleanup;
  298.  
  299.     // Actually send the report.
  300.     err = (*mHIDDispatchTable->pHIDSetReport)(mHIDDeviceConnectionRef, 
  301.                 kHIDFeatureReport, mReportID, reportPtr, reportSize);
  302.     if (err) goto setvaluecleanup;
  303.  
  304.     // Optional step. When a value is sent to a HID device, it usually does not
  305.     // reply with a new report showing the changed value. So if you are using 
  306.     // your report handler to keep track of current values, you may want to 
  307.     // request a new report for this item.
  308.     err = (*mHIDDispatchTable->pHIDGetReport)(mHIDDeviceConnectionRef, 
  309.                 kHIDFeatureReport, mReportID, MyHIDReportHandler, 0);
  310.     
  311. setvaluecleanup:
  312.     // HID Library makes it's own copy of our data, so we can free it now.
  313.     if (reportPtr != nil) DisposePtr((char *)reportPtr);
  314.  
  315.  
  316. // ***** Step 6: Display Output:
  317.     
  318.     // Allow time for reports to come in and be handled before quiting.
  319.     // The report handler will be given time to add output to USB Prober's
  320.     // Expert Log window.
  321. waitforinput:
  322.     printf("View report handling in USB Prober's Expert Log window.\n");
  323.     printf("Type 'q' to quit handling reports.\n");
  324.     fflush(nil);
  325.     int stopChar = 0;
  326.     while (stopChar != 'q' && stopChar != 'Q') stopChar = getchar();
  327.  
  328.  
  329. // ***** Step 7: Cleanup:
  330.     
  331. finalcleanup:                            
  332.     RemoveReportHandler();
  333.  
  334.     printf("Removed report handler.\n");
  335.     fflush(nil);
  336.  
  337.     if (mParsedHIDRef != nil)
  338.     {
  339.         HIDCloseReportDescriptor(mParsedHIDRef);
  340.         mParsedHIDRef = nil;
  341.     }
  342.  
  343.     return 0;
  344. }
  345.  
  346.  
  347. //        • ReportHandler
  348. //
  349. //    The key to how the report handler works is that when it gets called to 
  350. // process a report, it must pass the report through one of the HID Library
  351. // functions to extract the desired value. These decoder functions are called
  352. // HIDGetxxx (not HIDGetxxxCaps): HIDGetUsageValue, HIDGetScaledUsageValue,
  353. // HIDGetUsageValueArray, HIDGetButtons, and HIDGetButtonsOnPage. 
  354. //    Reports are compressed data that may contain multiple values within. They
  355. // also may or may not have reportID as the first byte of data. A brute force
  356. // way to handle the confusion is to just set up a series of decode calls for
  357. // each value you are interested in and apply the incomming report to each of
  358. // them, accepting only those that return with noErr.
  359.  
  360. void MyHIDReportHandler(void * inHIDReport, UInt32 inHIDReportLength, 
  361.                                     UInt32 inRefcon)
  362. {
  363.     // In the HID Browser, i have multiple devices open, each with it's own
  364.     // set of values. There i pass the pointer to the device's variables
  365.     // as the refcon.
  366.     #pragma unused(inRefcon)
  367.  
  368.     // A special note for using HIDGetButtonsOnPage: This call is going to
  369.     // return an array of HIDUsage's that corresponds to each button that is
  370.     // on. In the advent of there being no buttons on, rather than just
  371.     // returning a 0 length array, it comes back with kHIDUsageNotFoundErr.
  372.  
  373.     // We're making it easy. We're only looking for one specific value.
  374.     SInt32 reportValue;
  375.     OSErr err;
  376.  
  377.     err = HIDGetUsageValue(kHIDFeatureReport, targetUsagePage, mCollection, 
  378.                     targetUsage, &reportValue, mParsedHIDRef, inHIDReport, 
  379.                     inHIDReportLength);
  380.     if (err) return;
  381.  
  382.     // My first attempt at a cheap thing to do with our output, was to use printf.
  383.     // After crashing due to interference between printf's in the main code thread
  384.     // and this handler, i hit upon the much better expediant of sending our output
  385.     // to USB Prober's Expert Log window. Plus, this is a great example of an
  386.     // invaluable debugging tool for USB.
  387.     USBExpertStatusLevel(kUSBStatusLevelGeneral, 0, 
  388.                     "\pMyHIDReportHandler Value: ", (UInt32)reportValue);
  389. }
  390.  
  391.  
  392. //        • InstallReportHandler
  393. //
  394. //    Since InstallReportHandler can be called from various locations,
  395. // it does all it's own setup validation before doing the actual
  396. // install. If it fails, it leaves the state such that RemoveReportHandler
  397. // can also be called and perform only necessary cleanup.
  398.  
  399. void InstallReportHandler()
  400. {
  401.     HIDDeviceConnectionRef tempDeviceConnectionRef;
  402.     OSErr err;
  403.  
  404.     // Checking to see if device open already.
  405.     if (mHIDDeviceConnectionRef != 0) return;
  406.  
  407.     // Are we setup to open?
  408.     if (mHIDDispatchTable == nil) return;
  409.  
  410.     // Open the device.
  411.     err = (*mHIDDispatchTable->pHIDOpenDevice)
  412.                     (&tempDeviceConnectionRef, kHIDPerm_ReadWriteShared, 0);
  413.  
  414.     if (err) return;
  415.  
  416.     // The device is open, so let's install our handler.
  417.     err = (*mHIDDispatchTable->pHIDInstallReportHandler)
  418.                     (tempDeviceConnectionRef, 0, MyHIDReportHandler, 0);
  419.     
  420.     // Don't leave device open if we got an error.
  421.     if (err)
  422.     {
  423.         (*mHIDDispatchTable->pHIDCloseDevice)(tempDeviceConnectionRef);
  424.         return;
  425.     }
  426.     
  427.     // Signal successful opening.
  428.     mHIDDeviceConnectionRef = tempDeviceConnectionRef;
  429. }
  430.  
  431.  
  432. //        • RemoveReportHandler
  433. //
  434. //    Since RemoveReportHandler can be called from various locations,
  435. // it does all it's own setup validation before doing the actual remove.
  436.  
  437. void RemoveReportHandler()
  438. {
  439.     OSErr err;
  440.     
  441.     // If no indication that we successfully installed, don't remove.
  442.     if (mHIDDeviceConnectionRef == 0) return;
  443.     
  444.     // Remove the handler.
  445.     err = (*mHIDDispatchTable->pHIDRemoveReportHandler)(mHIDDeviceConnectionRef);
  446.  
  447.     // Removing the report handler also restores the previous handler, if any.
  448.     // In event of an error so that is not done, it may still be better to fall
  449.     // through and try to close the device anyway?
  450.     if (err) return;
  451.  
  452.     // Release the device.
  453.     err = (*mHIDDispatchTable->pHIDCloseDevice)(mHIDDeviceConnectionRef);
  454.  
  455.     if (err) return;
  456.     
  457.     // Signal successful closing.
  458.     mHIDDeviceConnectionRef = 0;
  459. }
  460.  
  461.